/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package maxbe.rel2xml.generator.docs.constraints;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import maxbe.rel2xml.generator.docs.DataType;

import org.apache.commons.lang3.RandomStringUtils;

/**
 * Generates random strings of random length between <tt>minLength</tt> and
 * <tt>maxLength</tt> with an optional fixed part.</br> With both random and
 * (start and/or end) constant parts specified, the start part will always
 * precede the random part as will the random part precede the end part.</br>
 * Represents the value constructor "random a-string[x..y]" or, if
 * <tt>numericsOnly</tt> is true, "random n-string[x..y]"
 * 
 * @author Max Bechtold
 * 
 */
public class RandomStrings implements ValueConstructor {

	private final int minLength;
	private final int maxLength;
	private final boolean numericsOnly;

	private final String fixStart;
	private final String fixRandom;
	private final String fixEnd;

	private final Random rand1;
	private final Random rand2;

	private static long seed1 = 321458552;
	private static long seed2 = 660430518;

	private static final List<DataType> types;

	static {
		types = new ArrayList<DataType>();
		types.add(DataType.VARCHAR);
		types.add(DataType.CHAR);
	}

	public RandomStrings(int min, int max) {
		this(min, max, false, null, null, null);
	}

	public RandomStrings(int min, int max, boolean numericsOnly) {
		this(min, max, numericsOnly, null, null, null);
	}

	public RandomStrings(int min, int max, boolean numericsOnly,
			String fixStart, String fixRandom, String fixEnd) {
		if (min > max) {
			max = min;
		}

		minLength = min;
		maxLength = max;

		this.numericsOnly = numericsOnly;
		rand1 = new Random(seed1++);
		rand2 = new Random(seed2++);

		this.fixStart = fixStart;
		this.fixRandom = fixRandom;
		this.fixEnd = fixEnd;
	}

	@Override
	public boolean lengthVaries() {
		return minLength != maxLength;
	}

	@Override
	public int getLength() {
		int length = maxLength;
		if (fixStart != null)
			length += fixStart.length();
		if (fixRandom != null)
			length += fixRandom.length();
		if (fixEnd != null)
			length += fixEnd.length();
		return length;
	}

	@Override
	public int getSubLength() {
		return -1;
	}

	@Override
	public boolean needsAnnounce() {
		return false;
	}

	@Override
	public void announce(int number) {
		// No preparation needed
	}

	@Override
	public void mark(int number) {
		// Ignore, always global
	}

	@Override
	public String next() {
		return generate(1).get(0);
	}

	@Override
	public List<String> next(int number) {
		return generate(number);
	}

	private List<String> generate(int number) {
		List<String> result = new ArrayList<String>();

		for (int i = 0; i < number; i++) {
			int length = minLength
					+ (minLength == maxLength ? 0 : rand1.nextInt(maxLength
							- minLength + 1));
			String random = null;

			if (numericsOnly) {
				random = RandomStringUtils.random(length, 0, 0, false, true,
						null, rand2);
			} else {
				random = RandomStringUtils.random(length, 0, 0, true,
						true, null, rand2);
			}

			result.add(attachFixParts(random));
		}

		return result;
	}

	private String attachFixParts(String base) {
		if (fixRandom != null) {
			int index = rand1.nextInt(base.length());
			if (index == 0) {
				base = fixRandom + base;
			} else if (index == base.length() - 1) {
				base = base + fixRandom;
			} else {
				base = base.substring(0, index) + fixRandom
						+ base.substring(index + 1);
			}
		}

		if (fixStart != null) {
			base = fixStart + base;
		}
		if (fixEnd != null) {
			base += fixEnd;
		}
		return base;
	}

	@Override
	public String toString() {
		String result = "random " + (numericsOnly ? "n-string " : "a-string ");
		result += String.format("[%d, %d]", minLength, maxLength);
		String fix = "";
		if (fixStart != null) {
			fix += "starting with " + fixStart;
		}
		if (fixRandom != null) {
			if (fix.length() > 0) {
				fix += ", ";
			}
			fix += "including " + fixRandom;
		}
		if (fixEnd != null) {
			if (fix.length() > 0) {
				fix += ", ";
			}
			fix += "ending with " + fixEnd;
		}
		if (fix.length() > 0) {
			result += fix;
		}
		return result;
	}

	@Override
	public List<DataType> getSupportedDataTypes() {
		return types;
	}
}
